/*
 * Copyright (c) 2023 fred
 * Licensed under the Apache License,Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
import { OutputSettings } from './OutputSettings'
import { LeafNode } from './LeafNode'
import { Node } from './Node'
import { Element } from './Element'
import { Validate } from '../helper/Validate'
import { StringUtil } from '../internal/StringUtil'
import { Document } from './Document'
import { Entities } from './Entities'
import logUtil from '../util/LogUtil'

/**
 A text node
 */
export class TextNode extends LeafNode {
  /**
   Create a new TextNode representing the supplied (unencoded) text).

   @param text raw text
   @see #createFromEncoded(String)
   */
  constructor(text:string) {
    super()
    this.value = text
  }

  public nodeName():string {
    return '#text'
  }

  /**
   * Get the text content of this text node.
   * @return Unencoded, normalised text.
   * @see TextNode#getWholeText()
   */
  public text():string {
    return StringUtil.normaliseWhitespace(this.getWholeText())
  }

  /**
   * Set the text content of this text node.
   * @param text unencoded text
   * @return this, for chaining
   */
  public setText(text:string):TextNode {
    this.setCoreValue(text)
    return this
  }

  /**
   Get the (unencoded) text of this text node, including any newlines and spaces present in the original.
   @return text
   */
  public getWholeText():string {
    return this.coreValue()
  }

  /**
   Test if this text node is blank -- that is, empty or only whitespace (including newlines).
   @return true if this document is empty or only whitespace, false if it contains any text content.
   */
  public isBlank():boolean {
    return StringUtil.isBlank(this.coreValue());
  }

  /**
   * Split this text node into two nodes at the specified string offset. After splitting, this node will contain the
   * original text up to the offset, and will have a new text node sibling containing the text after the offset.
   * @param offset string offset point to split node at.
   * @return the newly created text node containing the text after the offset.
   */
  public splitText(offset:number):TextNode {
    let text = this.coreValue()
    Validate.isTrue(offset >= 0, "Split offset must be not be negative");
    Validate.isTrue(offset < text.length, "Split offset must not be greater than current text length");
    let head = text.substring(0,offset)
    let tail = text.substring(offset)
    this.setText(head)
    let tailNode = new TextNode(tail)
    if (this.parentNode !== undefined && this.parentNode !== null) {
      this.parentNode.addChildren1(this.getSiblingIndex() + 1,[tailNode])
    }
    return tailNode
  }

  outerHtmlHead(accum: string[], depth: number, out: OutputSettings) {
    let prettyPrint = out.getPrettyPrint()
    let parent = this.parentNode instanceof Element ? this.parentNode as Element : null
    let normaliseWhite = prettyPrint && !Element.preserveWhitespace(this.parentNode)
    let trimLikeBlock = parent !== null && (parent.getTag().isBlock() || parent.getTag().formatAsBlock())
    let trimLeading = false
    let trimTrailing = false
    if (normaliseWhite) {
      trimLeading = (trimLikeBlock && this.siblingIndex === 0) || this.parentNode instanceof Document
      trimTrailing = trimLikeBlock && this.nextSibling() === null;
      // if this text is just whitespace, and the next node will cause an indent, skip this text:
      let next = this.nextSibling()
      let prev = this.previousSibling()
      let isBlank = this.isBlank()
      let couldSkip = (next instanceof Element &&(next as Element).shouldIndent(out))// next will indent
      || (next instanceof TextNode && (next as TextNode).isBlank())// next is blank text, from re-parenting
      || (prev instanceof Element &&(((prev as Element).isBlock()) || prev.isNode('br')))// br is a bit special - make sure we don't get a dangling blank line, but not a block otherwise wraps in head
      if (couldSkip && isBlank) {
        return
      }
      if ((this.siblingIndex === 0 && parent !== null && parent.getTag().formatAsBlock() && !isBlank) ||
      (out.getOutline() && this.siblingNodes().length > 0 && !isBlank) ||
      (this.siblingIndex > 0 && Node.isNode(prev,"br"))) {// special case wrap on inline <br> - doesn't make sense as a block tag
        this.indent(accum,depth,out)
      }
    }
    Entities.escape7(accum, this.coreValue(), out, false, normaliseWhite, trimLeading, trimTrailing);
  }

  outerHtmlTail(accum: string[], depth: number, out: OutputSettings) {}

  public toString():string {
    return this.outerHtml()
  }

  public clone():TextNode {
    return super.clone() as TextNode
  }

  isEffectivelyFirst():boolean {
    if (this.siblingIndex === 0) {
      return true
    }
    if (this.siblingIndex === 1) {
      let prev = this.previousSibling()
      return (prev instanceof TextNode) && ((prev as TextNode).isBlank())
    }
  }

  /**
   * Create a new TextNode from HTML encoded (aka escaped) data.
   * @param encodedText Text containing encoded HTML (e.g. {@code &lt;})
   * @return TextNode containing unencoded data (e.g. {@code <})
   */
  public static createFromEncoded(encodedText:string):TextNode {
    let text = Entities.unescape(encodedText)
    return new TextNode(text)
  }

  static normaliseWhitespace(text:string):string {
    text = StringUtil.normaliseWhitespace(text);
    return text;
  }

  static stripLeadingWhitespace(text:string):string {
    return text.replace(/^\s+/, "");
  }

  static lastCharIsWhitespace(sb: string[]): boolean {
    return sb.length !== 0 && sb[sb.length - 1] === ' '
  }
}
